/**********************************************************************
 * $Id: mitab_mifidxfile.cpp,v 1.8 2006/11/28 18:49:08 dmorissette Exp $
 *
 * Name:     mitab_mifidxfile.cpp
 * Project:  MapInfo TAB Read/Write library
 * Language: C++
 * Purpose:  Implementation of the MIFIDXFile class used to handle
 *           reading/writing of the .index file used by MITTAB only
 *           to read nth record in MID/MIF files faster.
 * Author:   Leaf Li, lironglu76@hotmail.com
 *
 **********************************************************************
 * Copyright (c) 2010, Leaf Li
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 **********************************************************************
 *
 *
 **********************************************************************/

#include "mitab.h"
#include "mitab_utils.h"

/*=====================================================================
 *                      class MIFIDXFile
 *====================================================================*/


/**********************************************************************
 *                   MIFIDXFile::MIFIDXFile()
 *
 * Constructor.
 **********************************************************************/
MIFIDXFile::MIFIDXFile()
{
    Reset();
}

/**********************************************************************
 *                   MIFIDXFile::~MIFIDXFile()
 *
 * Destructor.
 **********************************************************************/
MIFIDXFile::~MIFIDXFile()
{
    Close();
}


/**********************************************************************
 *                   MIFIDXFile::Reset()
 *
 * Reset all class memember to intial value.
 **********************************************************************/
void MIFIDXFile::Reset()
{
    m_fp = NULL;
    m_poIDBlock = NULL;
    m_nMaxId = -1;
    // Block size must be integral multiple of Data size.
    m_nBlockSize = 1800;
}

/**********************************************************************
 *                   MIFIDXFile::Open()
 *
 * Open a .ID file, and initialize the structures to be ready to read
 * objects from it.
 *
 * If the filename that is passed in contains a MIF or MID extension then
 * the extension will be changed to .ID before trying to open the file.
 *
 * Returns 0 on success, -1 on error.
 **********************************************************************/
int MIFIDXFile::Open(const char *pszFname, const char *pszAccess, GBool bTestOpenNoError /*= FALSE*/)
{
    int         nLen;

    if (m_fp)
    {
        if (!bTestOpenNoError)
            CPLError(CE_Failure, CPLE_FileIO,
                 "Open() failed: object already contains an open file");
        else
            CPLErrorReset();
        return -1;
    }

    /*-----------------------------------------------------------------
     * Validate access mode and make sure we use binary access.
     * Note that in Write mode we need TABReadWrite since we do random
     * updates in the index as data blocks are split
     *----------------------------------------------------------------*/
    if (EQUALN(pszAccess, "r", 1) && strchr(pszAccess, '+') != NULL)
    {
        m_eAccessMode = TABReadWrite;
        pszAccess = "rb+";
    }
    else if (EQUALN(pszAccess, "r", 1))
    {
        m_eAccessMode = TABRead;
        pszAccess = "rb";
    }
    else if (EQUALN(pszAccess, "w", 1))
    {
        m_eAccessMode = TABWrite;
        pszAccess = "wb+";
    }
    else
    {
        if (!bTestOpenNoError)
            CPLError(CE_Failure, CPLE_FileIO,
                     "Open() failed: access mode \"%s\" not supported", pszAccess);
        else
            CPLErrorReset();
        return -1;
    }

    /*-----------------------------------------------------------------
     * Change MIF or MID extension to .ID if necessary
     *----------------------------------------------------------------*/
    m_pszFname = CPLStrdup(pszFname);

    nLen = strlen(m_pszFname);
    if (nLen > 4 && strcmp(m_pszFname + nLen - 4, ".MIF")==0)
        strcpy(m_pszFname + nLen - 4, ".idx");
    else if (nLen > 4 && strcmp(m_pszFname + nLen - 4, ".MID")==0)
        strcpy(m_pszFname+nLen-4, ".idx");

#ifndef _WIN32
    TABAdjustFilenameExtension(m_pszFname);
#endif

    /*-----------------------------------------------------------------
     * Open file
     *----------------------------------------------------------------*/
    m_fp = VSIFOpen(m_pszFname, pszAccess);

    if (m_fp == NULL)
    {
        if (!bTestOpenNoError)
            CPLError(CE_Failure, CPLE_FileIO, "Open() failed for %s", m_pszFname);
        else
            CPLErrorReset();
        return -1;
    }

    if (m_eAccessMode == TABRead || m_eAccessMode == TABReadWrite)
    {
        VSIFSeek(m_fp, 0, SEEK_END);
        long filesize = VSIFTell(m_fp);

        m_nMaxId = (filesize - GetHeaderSize())/GetObjSize();
        if ( m_nMaxId < 0 )
        {
            CPLError(CE_Failure, CPLE_FileIO, "Index file %s is invalid.\n", m_pszFname);
            Close();
            return -1;
        }

        /*-------------------------------------------------------------
         * Read the first block from the file
         *------------------------------------------------------------*/
        m_poIDBlock = new TABRawBinBlock(m_eAccessMode, FALSE);

        if (m_nMaxId <= 0)
        {
            // .ID file size = 0 ... just allocate a blank block but
            // it won't get really used anyways.
            m_poIDBlock->InitNewBlock(m_fp, m_nBlockSize, 0);
        }
        else if (m_poIDBlock->ReadFromFile(m_fp, 0, m_nBlockSize) != 0)
        {
            // CPLError() has already been called.
            Close();
            return -1;
        }
    }
    else
    {
        /*-------------------------------------------------------------
         * WRITE access:
         * Get ready to write to the file
         *------------------------------------------------------------*/
        m_poIDBlock = new TABRawBinBlock(m_eAccessMode, FALSE);
        m_nMaxId = 0;
        m_poIDBlock->InitNewBlock(m_fp, m_nBlockSize, 0);

        Header header;
        WriteHeader(header);
    }

    return 0;
}

/**********************************************************************
 *                      MIFIDXFile::IsOpened()
 *
 * Checks whether mapinfo file is opened.
 *
 * Returns true if mapinfo file is opened. Otherwise, return 0.
 **********************************************************************/
bool MIFIDXFile::IsOpened()
{
    return (m_fp != NULL);
}

/**********************************************************************
 *                   MIFIDXFile::Reopen()
 *
 * Reopen index file, and initialize the structures to be ready to read
 * objects from it.
 *
 * Returns 0 on success, -1 on error.
 **********************************************************************/
int MIFIDXFile::Reopen(TABAccess eAccessMode)
{
    if (IsOpened() && m_eAccessMode == eAccessMode)
        return 0;
    char* pszFname = CPLStrdup(m_pszFname);

    int ret1 = Close();
    int ret2 = 0;

    const char* pszAccess;
    if (eAccessMode == TABRead)
        pszAccess = "r";
    else if (eAccessMode == TABWrite)
        pszAccess = "w";
    else
        pszAccess = "r+";

    ret2 = Open(pszFname, pszAccess);
    CPLFree(pszFname);

    return (ret1 == 0 && ret2 == 0 ? 0 : -1);
}

/**********************************************************************
 *                   MIFIDXFile::Close()
 *
 * Close current file, and release all memory used.
 *
 * Returns 0 on success, -1 on error.
 **********************************************************************/
int MIFIDXFile::Close()
{
    if (m_fp == NULL)
        return 0;

    Flush();
    
    if (m_poIDBlock)
    {
        delete m_poIDBlock;
        m_poIDBlock = NULL;
    }

    // Close file
    VSIFClose(m_fp);
    m_fp = NULL;

    CPLFree(m_pszFname);
    m_pszFname = NULL;

    Reset();
    return 0;
}

int MIFIDXFile::Flush()
{
    if (m_eAccessMode == TABRead || m_fp == NULL)
        return 0;

    if (m_poIDBlock)
        m_poIDBlock->CommitToFile();

    return VSIFFlush(m_fp);
}

GInt32 MIFIDXFile::GetMaxObjId(GBool bForce)
{
    if (bForce && m_eAccessMode != TABReadWrite && m_fp != NULL)
    {
        VSIFSeek(m_fp, 0, SEEK_END);
        long fileSize = VSIFTell(m_fp);
        m_nMaxId = (fileSize - GetHeaderSize())/GetObjSize();
    }
    return m_nMaxId; 
}

int MIFIDXFile::IsFileReadOnly()
{
    if (NULL == m_pszFname || m_pszFname[0] == 0)
        return -1;

    return VSIIsFileReadOnly(m_pszFname);
}

void MIFIDXFile::ReadFileInfo(FileInfo& fileinfo)
{
    fileinfo.Size = m_poIDBlock->ReadInt32();
    fileinfo.Year = m_poIDBlock->ReadInt16();
    fileinfo.Month = m_poIDBlock->ReadByte();
    fileinfo.Day = m_poIDBlock->ReadInt32();
    fileinfo.Hour = m_poIDBlock->ReadByte();
    fileinfo.Minute = m_poIDBlock->ReadByte();
    fileinfo.Second = m_poIDBlock->ReadByte();
}

int MIFIDXFile::WriteFileInfo(const FileInfo& fileinfo)
{
    int bResult1 = m_poIDBlock->WriteInt32(fileinfo.Size);
    int bResult2 = m_poIDBlock->WriteInt16(fileinfo.Year);
    int bResult3 = m_poIDBlock->WriteByte(fileinfo.Month);
    int bResult4 = m_poIDBlock->WriteInt32(fileinfo.Day);
    int bResult5 = m_poIDBlock->WriteByte(fileinfo.Hour);
    int bResult6 = m_poIDBlock->WriteByte(fileinfo.Minute);
    int bResult7 = m_poIDBlock->WriteByte(fileinfo.Second);

    if (bResult1 == 0 && bResult2 == 0 &&
        bResult3 == 0 && bResult4 == 0 &&
        bResult5 == 0 && bResult6 == 0 &&
        bResult7 == 0)
        return 0;
    else
        return -1;
}

void MIFIDXFile::ReadData(Data& data)
{
    data.Deleted = m_poIDBlock->ReadByte();
    data.MIFObjPtr = m_poIDBlock->ReadInt32();
    data.MIDObjPtr = m_poIDBlock->ReadInt32();
}

int MIFIDXFile::WriteData(const Data& data, GInt32 nFlag)
{
    int bResult1(0), bResult2(0), bResult3(0);
    if (nFlag & 0x00000001)
        bResult1 = m_poIDBlock->WriteByte(data.Deleted);
    else
        m_poIDBlock->ReadByte();

    if (nFlag & 0x00000002)
        bResult2 = m_poIDBlock->WriteInt32(data.MIFObjPtr);
    else
        m_poIDBlock->ReadInt32();

    if (nFlag & 0x00000004)
        bResult3 = m_poIDBlock->WriteInt32(data.MIDObjPtr);
    else
        m_poIDBlock->ReadInt32();

    if (bResult1 == 0 && bResult2 == 0 && bResult3 == 0)
        return 0;
    else
        return -1;
}

/**********************************************************************
 *                   MIFIDXFile::ReadHeader()
 *
 * Read the header of index file.
 *
 * Returns a value >= 0 on success, -1 on error.
 **********************************************************************/
int MIFIDXFile::ReadHeader(Header & header)
{
    if (m_poIDBlock == NULL)
        return -1;

    if (m_eAccessMode == TABWrite)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "ReadHeader() can't be used with Write access.");
        return -1;
    }

    if (m_poIDBlock->GotoByteInFile(0, TRUE) != 0)
        return -1;

    GInt32 nId = m_poIDBlock->ReadInt32();
    if (nId != MIF_INDEX_FILE_ID)
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                "File '%s' isn't a valid MIF/MID index file.",
                 m_pszFname);
        return -1;
    }

    GInt32 nVersion = m_poIDBlock->ReadInt32();
    if (nVersion == 1)
    {
        header.Version = 1;
        header.Platform = m_poIDBlock->ReadByte();
        ReadFileInfo(header.MIFFileInfo);
        ReadFileInfo(header.MIDFileInfo);
        header.PointCount = m_poIDBlock->ReadInt32();
        header.LineCount = m_poIDBlock->ReadInt32();
        header.RegionCount = m_poIDBlock->ReadInt32();
        header.TextCount = m_poIDBlock->ReadInt32();
        header.XMin = m_poIDBlock->ReadDouble();
        header.YMin = m_poIDBlock->ReadDouble();
        header.XMax = m_poIDBlock->ReadDouble();
        header.YMax = m_poIDBlock->ReadDouble();
    }
    else
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                "File version '%d' isn't supported.",
                 nVersion);
        return -1;
    }

    return 0;
}

/**********************************************************************
 *                   MIFIDXFile::WriteHeader()
 *
 * Write the header of index file.
 *
 * Returns a value >= 0 on success, -1 on error.
 **********************************************************************/
int MIFIDXFile::WriteHeader(const Header & header)
{
    if (m_poIDBlock == NULL)
        return -1;

    if (m_eAccessMode != TABReadWrite && m_eAccessMode != TABWrite)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "WriteHeader() can be used only with Write or ReadWrite access.");
        return -1;
    }

    if (m_poIDBlock->GotoByteInFile(0) != 0)
        return -1;

    int bResult1 = m_poIDBlock->WriteInt32(header.ID);
    int bResult2 = m_poIDBlock->WriteInt32(header.Version);
    int bResult3 = m_poIDBlock->WriteByte(header.Platform);
    int bResult4 = WriteFileInfo(header.MIFFileInfo);
    int bResult5 = WriteFileInfo(header.MIDFileInfo);
    int bResult6 = m_poIDBlock->WriteInt32(header.PointCount);
    int bResult7 = m_poIDBlock->WriteInt32(header.LineCount);
    int bResult8 = m_poIDBlock->WriteInt32(header.RegionCount);
    int bResult9 = m_poIDBlock->WriteInt32(header.TextCount);
    int bResult10 = m_poIDBlock->WriteDouble(header.XMin);
    int bResult11 = m_poIDBlock->WriteDouble(header.YMin);
    int bResult12 = m_poIDBlock->WriteDouble(header.XMax);
    int bResult13 = m_poIDBlock->WriteDouble(header.YMax);
    int bResult14 = m_poIDBlock->WriteZeros(GetHeaderSize() - m_poIDBlock->GetCurAddress());;

    if (bResult1 == 0 && bResult2 == 0 &&
        bResult3 == 0 && bResult4 == 0 &&
        bResult5 == 0 && bResult6 == 0 &&
        bResult7 == 0 && bResult8 == 0 &&
        bResult8 == 0 && bResult9 == 0 &&
        bResult10 == 0 && bResult11 == 0 &&
        bResult12 == 0 && bResult13 == 0 &&
        bResult14 == 0)
        return 0;
    else
        return -1;
}

/**********************************************************************
 *                   MIFIDXFile::UpdateHeader()
 *
 * Write MIF/MID file information in the header of index file.
 *
 * Returns a value >= 0 on success, -1 on error.
 **********************************************************************/
int MIFIDXFile::UpdateHeader(const FileInfo &mif, const FileInfo& mid)
{
    if (m_poIDBlock == NULL)
        return -1;

    if (m_eAccessMode != TABReadWrite && m_eAccessMode != TABWrite)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "UpdateHeader() can be used only with Write or ReadWrite access.");
        return -1;
    }

    // Skip index file id and version field.
    if (m_poIDBlock->GotoByteInFile(8) != 0)
        return -1;

    int bResult1 = WriteFileInfo(mif);
    int bResult2 = WriteFileInfo(mid);

    if (bResult1 == 0 && bResult2 == 0)
        return 0;
    else
        return -1;
}

/**********************************************************************
 *                   MIFIDXFile::NeedToUpdate()
 *
 * Check whether it is a valid index file.
 *
 * Returns 1 if it is a valid index file, 0 when it isn't a valid index
 * file, -1 on error.
 **********************************************************************/
int MIFIDXFile::IsValid(const Header& header)
{
    if (m_fp == NULL)
        return -1;

    MIFIDXFile::Header oldHeader;
    if (ReadHeader(oldHeader) != 0)
        return -1;

    if (header.ID == oldHeader.ID &&
        header.MIDFileInfo == oldHeader.MIDFileInfo && 
        header.MIFFileInfo == oldHeader.MIFFileInfo &&
        header.Platform == oldHeader.Platform)
        return 1;
    else
        return 0;
}

/**********************************************************************
 *                   MIFIDXFile::GetMIFObjPtr()
 *
 * Read index data of .MIF/.MID files.
 *
 * Note that object ids are positive and start at 1.
 *
 * Returns a value >= 0 on success, -1 on error.
 **********************************************************************/
GInt32 MIFIDXFile::GetObjPtr(GInt32 nObjId, Data& data)
{
    if (m_poIDBlock == NULL)
        return -1;

    if (nObjId < 1 || nObjId > m_nMaxId)
    {
        CPLError(CE_Failure, CPLE_IllegalArg,
                 "GetObjPtr(): Invalid object ID %d (valid range is [1..%d])",
                 nObjId, m_nMaxId);
        return -1;
    }

    if (m_poIDBlock->GotoByteInFile( GetOffset(nObjId), TRUE ) != 0)
        return -1;

    ReadData(data);

    return 0;
}

int MIFIDXFile::GetObjPtr(GInt32 nObjId, GInt32& nMIFObjPtr, GInt32& nMIDObjPtr)
{
    Data data;
    int nResult = GetObjPtr(nObjId, data);
    nMIFObjPtr= data.MIFObjPtr;
    nMIDObjPtr = data.MIDObjPtr;
    return nResult;
}

int MIFIDXFile::IsObjPtrDeleted(GInt32 nObjId, bool& bDeleted)
{
    Data data;
    int nResult = GetObjPtr(nObjId, data);
    bDeleted = (data.Deleted > 0);
    return nResult;
}

/**********************************************************************
 *                   MIFIDXFile::GetMIFObjPtr()
 *
 * Return the offset in the index file where the map object with the
 * specified id is located.
 *
 * Note that object ids are positive and start at 1.
 *
 * Returns a value >= 0 on success, -1 on error.
 **********************************************************************/
GInt32 MIFIDXFile::GetMIFObjPtr(GInt32 nObjId)
{
    Data data;
    int nResult = GetObjPtr(nObjId, data);
    if (nResult == 0) 
        return data.MIFObjPtr;
    else
        return -1;
}


/**********************************************************************
 *                   MIFIDXFile::GetMIDObjPtr()
 *
 * Return the offset in the index file where the map object with the
 * specified id is located.
 *
 * Note that object ids are positive and start at 1.
 *
 * Returns a value >= 0 on success, -1 on error.
 **********************************************************************/
GInt32 MIFIDXFile::GetMIDObjPtr(GInt32 nObjId)
{
    Data data;
    int nResult = GetObjPtr(nObjId, data);
    if (nResult == 0) 
        return data.MIDObjPtr;
    else
        return -1;
}

/**********************************************************************
 *                   MIFIDXFile::SetObjPtr()
 *
 * Write index data of .MIF/.MID files.
 *
 * Note that object ids are positive and start at 1.
 *
 * Returns a value of 0 on success, -1 on error.
 **********************************************************************/
int MIFIDXFile::SetObjPtr(GInt32 nObjId, const Data& data, GInt32 nFlag /*= 0xFFFFFFFF*/)
{
    if (m_poIDBlock == NULL)
        return -1;

    if (m_eAccessMode != TABReadWrite && m_eAccessMode != TABWrite)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "SetObjPtr() can be used only with Write or ReadWrite access.");
        return -1;
    }

    if (nObjId < 1)
    {
        CPLError(CE_Failure, CPLE_IllegalArg,
               "SetObjPtr(): Invalid object ID %d (must be greater than zero)",
                 nObjId);
        return -1;
    }

    /*-----------------------------------------------------------------
     * GotoByteInFile() will automagically commit current block and init
     * a new one if necessary.
     *----------------------------------------------------------------*/
    GInt32 nLastIdBlock = GetOffset(m_nMaxId) / m_nBlockSize;
    GInt32 nTargetIdBlock = GetOffset(nObjId) / m_nBlockSize;
    if (m_nMaxId > 0 && nTargetIdBlock <= nLastIdBlock)
    {
        /* Pass second arg to GotoByteInFile() to force reading from file
         * when going back to blocks already committed
         */
        if (m_poIDBlock->GotoByteInFile( GetOffset(nObjId), TRUE ) != 0)
            return -1;
    }
    else
    {
        /* If we reach EOF then a new empty block will have to be allocated
         */
        if (m_poIDBlock->GotoByteInFile( GetOffset(nObjId) ) != 0)
            return -1;
    }

    m_nMaxId = MAX(m_nMaxId, nObjId);

    return WriteData(data, nFlag);
}

/**********************************************************************
 *                   MIFIDXFile::UpdateObjPtr()
 *
 * Update object ptr to add the specified offset from the specified 
 * feature.
 *
 * Note that object ids are positive and start at 1.
 *
 * Returns a value of 0 on success, -1 on error.
 **********************************************************************/
int MIFIDXFile::UpdateObjPtr(GInt32 nObjId, GInt32 nMIFOffset, GInt32 nMIDOffset)
{
    if (nMIFOffset == 0 && nMIDOffset == 0)
        return 0;

    if (m_poIDBlock == NULL)
        return -1;

    if (m_eAccessMode != TABReadWrite)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "UpdateObjPtr() can be used only with ReadWrite access.");
        return -1;
    }

    if (nObjId < 1)
    {
        CPLError(CE_Failure, CPLE_IllegalArg,
               "UpdateObjPtr(): Invalid object ID %d (must be greater than zero)",
                 nObjId);
        return -1;
    }

    GInt32 nOldMIFObjPtr, nMIDObjPtr;
    while(nObjId <= m_nMaxId)
    {
        if (GetObjPtr(nObjId, nOldMIFObjPtr, nMIDObjPtr) == 0)
            SetObjPtr(nObjId, nOldMIFObjPtr + nMIFOffset, nMIDObjPtr + nMIDOffset);
        nObjId++;
    }

    return 0;
}

/**********************************************************************
 *                   MIFIDXFile::DeleteObjPtr()
 *
 * Set the deleted marker to 1 for the specified object ptr. It doesn't
 * really remove the specified object ptr from index file. If you want
 * to remove it from the index file, please call RemoveObjPtr().
 *
 * Note that object ids are positive and start at 1.
 *
 * Returns a value of 0 on success, -1 on error.
 **********************************************************************/
int MIFIDXFile::DeleteObjPtr(GInt32 nObjId)
{
    if (m_eAccessMode != TABReadWrite)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "DeleteObjPtr() can be used only with ReadWrite access.");
        return -1;
    }

    if (nObjId < 1)
    {
        CPLError(CE_Failure, CPLE_IllegalArg,
               "DeleteObjPtr(): Invalid object ID %d (must be greater than zero)",
                 nObjId);
        return -1;
    }

    if (nObjId > m_nMaxId)
    {
        CPLError(CE_Failure, CPLE_IllegalArg,
               "DeleteObjPtr(): Invalid object ID %d (must be less than max object id)",
                 nObjId);
        return -1;
    }

    return SetObjPtrDeleted(nObjId);
}

/**********************************************************************
 *                 MIFIDXFile::GetDeletedObjectCount()
 *
 * Get the real object ptr count without counting the deleted object ptr,
 * whose deleted marker is 1.
 *
 * Note that object ids are positive and start at 1.
 *
 * Returns the count of the deleted object ptr.
 **********************************************************************/
int MIFIDXFile::GetRealObjectCount()
{
    return (GetMaxObjId() - GetDeletedObjectCount());
}

/**********************************************************************
 *                 MIFIDXFile::GetDeletedObjectCount()
 *
 * Get the count of the deleted object ptr, whose deleted marker
 * is 1.
 *
 * Note that object ids are positive and start at 1.
 *
 * Returns the count of the deleted object ptr.
 **********************************************************************/
int MIFIDXFile::GetDeletedObjectCount()
{
    bool bDeleted = false;
    int ret = 0;
    for (GInt32 i = 1; i <= m_nMaxId; ++i)
    {
        if (IsObjPtrDeleted(i, bDeleted) == 0 && bDeleted)
            ret++;
    }
    return ret;
}

/**********************************************************************
 *                   MIFIDXFile::GetDeletedObjectPtr()
 *
 * Get an object id list of the deleted object ptr, whose deleted marker
 * is 1.
 *
 * Note that object ids are positive and start at 1.
 *
 * Returns a value of 0 on success, -1 on error.
 **********************************************************************/
int MIFIDXFile::GetDeletedObjectPtr(ObjectIdList& ids)
{
    bool bDeleted = false;
    int ret = 0;
    for (GInt32 i = 1; i <= m_nMaxId; ++i)
    {
        if (IsObjPtrDeleted(i, bDeleted) == 0 && bDeleted)
            ids.push_back(i);
        else
            ret = -1;
    }
    return 0;
}

/**********************************************************************
 *                   MIFIDXFile::DeleteObjPtr()
 *
 * Delete the specified object ptr and move object ptr after the specified
 * object ptr forward.
 *
 * Note that object ids are positive and start at 1.
 *
 * Returns a value of 0 on success, -1 on error.
 **********************************************************************/
int MIFIDXFile::RemoveObjPtr(GInt32 nObjId)
{
    if (m_poIDBlock == NULL)
        return -1;

    if (m_eAccessMode != TABReadWrite)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "RemoveObjPtr() can be used only with ReadWrite access.");
        return -1;
    }

    if (nObjId < 1)
    {
        CPLError(CE_Failure, CPLE_IllegalArg,
               "RemoveObjPtr(): Invalid object ID %d (must be greater than zero)",
                 nObjId);
        return -1;
    }

    if (nObjId < m_nMaxId -1)
    {
        GInt32 nMIFPos1(-1), nMIDPos1(-1), nMIFPos2(-1), nMIDPos2(-1), nMIFPos3(-1), nMIDPos3(-1);
        if ( GetObjPtr(nObjId, nMIFPos1, nMIDPos1) != 0 ||
             GetObjPtr(nObjId+1, nMIFPos2, nMIDPos2) != 0 ||
             GetObjPtr(nObjId+2, nMIFPos3, nMIDPos3) != 0)
        {
            CPLError(CE_Failure, CPLE_FileIO, "Failed reading index data!");
            return -1;
        }
        GInt32 nMIFOffset = nMIFPos2 - nMIFPos1;
        GInt32 nMIDOffset = nMIDPos2 - nMIDPos1;

        if ((nMIFPos3 - nMIFPos2) != nMIFOffset || (nMIDPos3 - nMIDPos2) != nMIDOffset)
        {
            GInt32 nMIFObjPtr, nMIDObjPtr;
            while(nObjId < m_nMaxId)
            {
                if (GetObjPtr(nObjId+1, nMIFObjPtr, nMIDObjPtr) == 0)
                    SetObjPtr(nObjId, nMIFObjPtr - nMIFOffset, nMIDObjPtr - nMIDOffset);
                nObjId++;
            }
        }
    }

    m_poIDBlock->CommitToFile();
    VSIChSize(m_fp, GetOffset(m_nMaxId));
    m_nMaxId--;

    return 0;
}

/**********************************************************************
 *                   MIFIDXFile::RemoveObjPtr()
 *
 * Delete the specified object ptr and move object ptr after the specified
 * object ptr forward.
 *
 * Note that object ids are positive and start at 1.
 *
 * Returns a value of 0 on success, -1 on error.
 **********************************************************************/
int MIFIDXFile::RemoveObjPtr(ObjectIdList& ids)
{
    if (ids.size() == 0)
        return 0;

    if (m_poIDBlock == NULL)
        return -1;

    if (m_eAccessMode != TABReadWrite)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "RemoveObjPtr() can be used only with ReadWrite access.");
        return -1;
    }

    // Merget the continuous ids so that it is more efficient to move data
    std::vector<std::pair<GInt32, GInt32> > newIds;
    size_t nPos1(0), nPos2(0);
    while( nPos2 < ids.size() - 1)
    {
        if (ids[nPos2] < 1)
        {
            CPLError(CE_Failure, CPLE_IllegalArg,
                   "RemoveObjPtr(): Invalid object ID %d (must be greater than zero)",
                     ids[nPos2]);
            return -1;
        }

        if (ids[nPos2] > m_nMaxId)
        {
            CPLError(CE_Failure, CPLE_IllegalArg,
                   "RemoveObjPtr(): Invalid object ID %d (must be less than max object id)",
                     ids[nPos2]);
            return -1;
        }

        if (ids[nPos2+1] - ids[nPos2] != 1)
        {
            newIds.push_back(std::pair<GInt32, GInt32>(ids[nPos1], ids[nPos2]));
            nPos1 = nPos2 + 1;
        }
        nPos2++;
    }
    newIds.push_back(std::pair<GInt32, GInt32>(ids[nPos1], ids[nPos2]));

    size_t nCount = newIds.size();
    GInt32 nStartObjId(-1), nEndObjId(-1), nStopObjId(-1);
    GInt32 nDeletedFeatures = 0;
    GInt32 nMIFOffset = 0;
    GInt32 nMIDOffset = 0;
    for (size_t i = 0; i < newIds.size(); ++i)
    {
        nStartObjId = newIds[i].first;
        nEndObjId = newIds[i].second;
        nDeletedFeatures += (nEndObjId - nStartObjId + 1);
        if (i < newIds.size() - 1)
            nStopObjId = newIds[i+1].first;
        else
            nStopObjId = m_nMaxId -1;

        GInt32 nMIFPos1(-1), nMIDPos1(-1), nMIFPos2(-1), nMIDPos2(-1);
        if ( nEndObjId <= nStopObjId && nEndObjId < m_nMaxId )
        {
            if (GetObjPtr(nStartObjId, nMIFPos1, nMIDPos1) == 0 &&
                GetObjPtr(nEndObjId+1, nMIFPos2, nMIDPos2) == 0)
            {
                nMIFOffset += nMIFPos2 - nMIFPos1;
                nMIDOffset += nMIDPos2 - nMIDPos1;
                nStartObjId -= (nDeletedFeatures - 1);

                GInt32 nMIFObjPtr, nMIDObjPtr;
                while(nEndObjId <= nStopObjId)
                {
                    if (GetObjPtr(++nEndObjId, nMIFObjPtr, nMIDObjPtr) == 0)
                        SetObjPtr(nStartObjId++, nMIFObjPtr - nMIFOffset, nMIDObjPtr - nMIDOffset);
                }
            }
            else
            {
                if (nDeletedFeatures > 0)
                {
                    m_poIDBlock->CommitToFile();
                    m_nMaxId -= nDeletedFeatures;
                    VSIChSize(m_fp, GetOffset(m_nMaxId+1));
                }
                CPLError(CE_Failure, CPLE_FileIO, "Failed reading index data!");
                return -1;
            }
        }
    }

    m_poIDBlock->CommitToFile();
    m_nMaxId -= nDeletedFeatures;
    VSIChSize(m_fp, GetOffset(m_nMaxId+1));

    return 0;
}


int MIFIDXFile::SetObjPtr(GInt32 nObjId, GInt32 nMIFObjPtr, GInt32 nMIDObjPtr)
{
    Data data;
    data.Deleted = 0;
    data.MIFObjPtr = nMIFObjPtr;
    data.MIDObjPtr = nMIDObjPtr;
    return SetObjPtr(nObjId, data, 0x00000006);
}

/**********************************************************************
 *                   MIFIDXFile::SetMIFObjPtr()
 *
 * Set the offset in the index file where the map object with the
 * specified id is located.
 *
 * Note that object ids are positive and start at 1.
 *
 * Returns a value of 0 on success, -1 on error.
 **********************************************************************/
int MIFIDXFile::SetMIFObjPtr(GInt32 nObjId, GInt32 nMIFObjPtr)
{
    Data data;
    data.Deleted = 0;
    data.MIFObjPtr = nMIFObjPtr;
    data.MIDObjPtr = -1;
    return SetObjPtr(nObjId, data, 0x00000002);
}

/**********************************************************************
 *                   MIFIDXFile::SetMIDObjPtr()
 *
 * Set the offset in the index file where the map object with the
 * specified id is located.
 *
 * Note that object ids are positive and start at 1.
 *
 * Returns a value of 0 on success, -1 on error.
 **********************************************************************/
int MIFIDXFile::SetMIDObjPtr(GInt32 nObjId, GInt32 nMIDObjPtr)
{
    Data data;
    data.Deleted = 0;
    data.MIFObjPtr = -1;
    data.MIDObjPtr = nMIDObjPtr;
    return SetObjPtr(nObjId, data, 0x00000004);
}

int MIFIDXFile::SetObjPtrDeleted(GInt32 nObjId)
{
    Data data;
    data.Deleted = 1;
    data.MIFObjPtr = -1;
    data.MIDObjPtr = -1;
    return SetObjPtr(nObjId, data, 0x00000001);
}

/**********************************************************************
 *                   MIFIDXFile::Dump()
 *
 * Dump block contents... available only in DEBUG mode.
 **********************************************************************/
#ifdef DEBUG

void MIFIDXFile::Dump(FILE *fpOut /*=NULL*/)
{
    if (fpOut == NULL)
        fpOut = stdout;

    fprintf(fpOut, "----- MIFIDXFile::Dump() -----\n");

    if (m_fp == NULL)
    {
        fprintf(fpOut, "File is not opened.\n");
    }
    else
    {
        fprintf(fpOut, "File is opened: %s\n", m_pszFname);
        fprintf(fpOut, "Current index block follows ...\n\n");
        m_poIDBlock->Dump(fpOut);
        fprintf(fpOut, "... end of index block.\n\n");

    }

    fflush(fpOut);
}

#endif // DEBUG





